Read Buf

Read Buf

如何将 Python 包发布到 PyPI 的综合指南

在本指南中,我将向你展示如何轻松地将包发布到 Python 包索引(PyPI),以便其他人可以安装和使用你的作品。一旦包在 PyPI 上可用,任何人都可以运行 pip install 来安装该包到他们的机器上。这不仅允许任何人将你的代码作为库导入到他们自己的模块中,你还可以通过 PyPI 发布命令行工具

注意:这些说明主要针对 Mac 和 Linux 平台。我认为它们在 Windows 上的差别不大,但由于我不常在 Windows 上进行开发工作,所以不太了解该平台的细节。

代 码

首先,你需要一些代码来发布!我创建了一个最小的项目,你可以用作起点。你可以在这里找到代码。让我们来看看。

首先让我们浏览一下文件夹结构:

├── README.md
├── package_boilerplate
│   ├── __init__.py
│   └── importable.py
├── requirements.txt
├── scripts
│   └── boilerplate-cli
├── setup.py
└── version.py

这些是 Python 包内部的所有文件。首先,我们有一个名为 package_boilerplate 的目录。这个目录包含了我们模块的所有代码。目录外的所有文件都是包本身的元数据,主要用于包发布过程、安装依赖项等。我们将很快介绍其他文件。

README.md 是包含包详细信息的 Markdown 格式文本。这个内容通常会被渲染为 HTML 并在 GitHub 和 PyPI 等地方显示。

如果我们查看 __init__.py,我们会看到以下内容:

from colored import fg, bg, attr

def main():
    print(f"{fg('dark_orange')}{attr('bold')}You called me!{attr('reset')}")

在这里我们做了几件事:首先,我们导入了一个名为 colored 的库。colored 是一个用于给程序终端输出上色的工具。很有趣吧!

基本上,如果调用 main 函数,这个文件将在终端打印一些彩色文本。如果你继续阅读,你将看到我们如何通过 pip 将其发布为命令行工具!

接下来,让我们看看 importable.py

from colored import fg, bg, attr

print(f"{fg('orchid')}{attr('bold')}You imported me!{attr('reset')}")

同样,我们只是向终端打印一些彩色文本,但这次我们可以在外部模块中导入这段代码。由于代码不在函数中,一旦文件被导入,文本将立即被打印。

好的,这就是我们程序的逻辑。让我们继续浏览文件夹结构,看看还有什么是发布所需的。

下一个文件是 requirements.txt。如果你曾经编写过依赖于其他外部 Python 包的模块,那么你可能对这个文件很熟悉。该文件用于告诉 pip 应该安装哪些版本的模块,如果你运行 pip install -r requirements.txt,它会一次性安装所有模块。Python 包也不例外;如果它们依赖于其他模块,pip 需要知道要安装哪些模块。我们将在 setup.py 中引用这个文件。

接下来,我们有 scripts 目录。目录名称并不重要,也不是必需的,但我更喜欢将内容分开和组织好。在这个文件夹中,我们有一个名为 boilerplate-cli 的文件。注意我们没有给文件添加 .py 后缀,而是使用了 boilerplate-cli 的命名约定,而不是 boilerplate_cli。这是因为这个文件将作为命令行程序安装到用户的 PATH 中。我们希望用户能够通过运行 boilerplate-cli 来调用程序,而不是 boilerplate_cli.py,因为这是命令行程序的预期命名约定。

在这个文件中,我们有以下内容:

#!/usr/bin/env python

from package_boilerplate import main

main()

第一行很重要,因为我们必须告诉用户的操作系统这是什么类型的代码,因为它没有文件扩展名。这被称为 Shebang。接下来,我们从 __init__.py 导入了程序的入口点 main 并运行了该函数。

调用这个函数会启动我们的程序!目前它只是在终端打印文本,但它可以是更复杂程序的入口点。我们将在 setup.py 部分引用这个文件,使其作为命令行程序可供用户使用。

这个过程中最重要的部分是 setup.py 文件。它告诉 pip 如何打包该包以及所有的设置和依赖项。

"""Module setup."""

import runpy
from setuptools import setup, find_packages

PACKAGE_NAME = "package-boilerplate"
version_meta = runpy.run_path("./version.py")
VERSION = version_meta["__version__"]

with open("README.md", "r") as fh:
    long_description = fh.read()

def parse_requirements(filename):
    """Load requirements from a pip requirements file."""
    lineiter = (line.strip() for line in open(filename))
    return [line for line in lineiter if line and not line.startswith("#")]

if __name__ == "__main__":
    setup(
        name=PACKAGE_NAME,
        version=VERSION,
        packages=find_packages(),
        install_requires=parse_requirements("requirements.txt"),
        python_requires=">=3.6.3",
        scripts=["scripts/boilerplate-cli"],
        description="This is a description.",
        long_description=long_description,
        long_description_content_type="text/markdown",
    )

首先,我们导入了一些名为 runpysetuptools 的实用工具。runpy 内置于 Python,但 setuptools 需要安装,我们将在下一节中讨论。

接下来,我们从 version.py 文件中获取 Python 元数据并获得当前版本号 VERSION。这个文件只是包含你正在发布包的当前版本号。我们将在下一节讨论为什么它被分成一个独立的文件。

接下来,从 README.md 中提取 long_description。使用 README 中的内容意味着这个描述只需要存在一个地方。

然后,我添加了一个名为 parse_requirements 的辅助函数。Python 的 setup.py 格式期望 install_requires 属性包含该包依赖项的列表。由于维护包含依赖项的 requirements.txt 文件以及在这里的依赖项列表比较麻烦,这个 parse_requirements 函数简单地从 requirements.txt 导入依赖项,这样你只需要在一个地方维护它们。

最后,我们调用 setup 并传入一些值来定义包。指定 packages=find_packages() 的部分指向名为 package_boilerplate 的目录,并由此函数自动发现。nameversion 用于 PyPI 目录列表,并且也是人们想要安装包时引用的内容。install_requiresrequirements.txt 文件中提取依赖项列表。python_requires=">=3.6.3" 是用户必须安装的 Python 版本。最后,scripts 是你指定应为用户安装的命令行程序的地方。

你还应该设置其他值,例如描述和作者。你可以在这里阅读有关可用设置值的更多信息。

最后一个文件 version.py 非常小。这个文件有两个用途:首先,它在上面的 setup.py 模块中被导入,用作包版本;其次,版本值存储在 __version__ 属性中。推荐这样做,以便可以通过编程方式发现包的版本。

提示:在工作过程中,你可以在发布之前本地安装你的包。这样,你将在提交到 PyPI 之前知道它是否工作。要本地安装包,你可以在包目录中运行 pip install -e .

发布你的包

所以你已经编写了包,并且希望发布它!我们快完成了。还有几个步骤可以发布你的包。

首先需要做的是通过运行 pip install twine 安装一个名为 twine 的工具。twine 是一个用于在 PyPI 上发布 Python 包的工具。然后,我们还需要安装 setuptools,因为我们在设置脚本中使用了它:pip install setuptools

现在,我们需要创建一个将要发布的发行包。为此,运行 python setup.py sdist bdist_wheel,这将创建 builddist 目录。

构建完成后,我们可以使用 twine 检查是否有任何错误或警告。运行 twine check dist/* 检查发行包是否有错误。如果一切顺利,你应该会看到:

Checking distribution dist/package_boilerplate-1.0.0-py3-none-any.whl: Passed
Checking distribution dist/package-boilerplate-1.0.0.tar.gz: Passed

完成后,我们希望将包发布到 PyPI 测试站点。在发布到真实索引之前,我们可以发布到 test.pypi.org 以确保一切正常!为此,我们可以指示 twine 使用不同的存储库,如下所示:twine upload --repository-url https://test.pypi.org/legacy/ dist/*

如果我们现在运行这个命令,我们将看到以下内容:

Enter your username:

看起来我们需要创建一个帐户!所以前往 PyPI 测试站点的注册页面并创建一个帐户。如果你再次运行命令并输入你的用户名和密码,希望你能看到:

Uploading distributions to https://test.pypi.org/legacy/
Uploading package_boilerplate-1.0.0-py3-none-any.whl
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4.95k/4.95k [00:00<00:00, 37.2kB/s]
Uploading package-boilerplate-1.0.0.tar.gz
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4.11k/4.11k [00:01<00:00, 3.76kB/s]

如果是这样,恭喜你!你已成功发布你的包!

注意:确保你的包名称是唯一的!

剩下的就是将你的包发布到官方的 pypi.org。为此,你只需要在那里创建另一个帐户,并在不指定不同存储库的情况下发布你的包,如下所示:twine upload dist/*

安装你的包

现在你的包已经发布,我打赌你希望安装它看看效果如何!让我们从 PyPI 测试索引安装我们的包:pip install --index-url https://test.pypi.org/simple/ package-boilerplate。将来,如果我们不提供 --index-url 参数,那么我们将请求官方索引中的包。如果一切顺利,你应该会看到:

Looking in indexes: https://test.pypi.org/simple/
Collecting package-boilerplate
  Downloading https://test-files.pythonhosted.org/packages/10/63/208bbd4ea4427f5eb0e4e6248973b387f1dec4ed60333c4daf416c310903/package_boilerplate-1.0.0-py3-none-any.whl
Requirement already satisfied: colored==1.3.93 in /usr/local/lib/python3.7/site-packages (from package-boilerplate) (1.3.93)
Installing collected packages: package-boilerplate
Successfully installed package-boilerplate-1.0.0

如果是这样,那么包已安装!所以让我们使用它。记得我们创建的命令行程序吗?现在我们应该能够通过调用脚本名称从任何地方运行它:

$ boilerplate-cli

文本是橙色的,就像我们想要的一样!如果我们想在另一个项目中导入模块呢?让我们试试:

$ python
Python 3.7.3 (default, Mar 27 2019, 09:23:15) 
[Clang 10.0.1 (clang-1001.0.46.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

它工作了!而且我们甚至得到了彩色文本。

一旦你将包发布到官方 PyPI 索引,你将能够通过运行 pip install package-boilerplate 安装该包。当然,你会希望在整个过程中选择一个不同的包名称并在此处使用它。